using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;

namespace Blimpbots.Recognition
{
    public partial class Train : Form
    {
        private string _workingPath;
        private string _modelPath;

        RecognitionModel activeModel = null;

        RecognitionModel.Example _activeExample = null;
        RecognitionModel.Example activeExample {

            set {
                _activeExample = value;

                lstNamedPoints.Items.Clear();

                if (value == null)
                {
                    sceneBox.Scene = null;
                    sceneBox.InterestPoints = new List<PictureBox2.InterestPoint>();
                    sceneBox.Outline = new List<Point>();
                    sceneBox.NamedPoints = new Dictionary<string, Point>();
                    return;
                }

                foreach (string name in _activeExample.NamedPoints.Keys)
                    lstNamedPoints.Items.Add(name);

                sceneBox.NamedPoints = _activeExample.GetPointDictionary();
                sceneBox.Outline = _activeExample.ObjectOutline;
                sceneBox.InterestPoints = BuildInterestPts(_activeExample);
                sceneBox.Scene = _activeExample.Image;
            }
            get {
                return _activeExample;
            }            
        }

        public Train(string camera_ip)
        {
            System.IO.DirectoryInfo di
                = new System.IO.DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory + "..\\..\\..\\");

            _workingPath = di.FullName;

            InitializeComponent();
            this.txtIP.Text = camera_ip;

            // TODO: these are dummy properties
            CameraProperties cp = new CameraProperties();
            c = new LiveCamera(640, 480, txtIP.Text, cp);

            c.NewFrame += new EventHandler(c_NewFrame);

        }

        private void OpenModels()
        {
            if (!Directory.Exists(_modelPath))
            {
                try
                {
                    Directory.CreateDirectory(_modelPath);
                }
                catch
                {
                    throw new Exception("Could not open or create 'Models' directory.");
                }
            }

            DirectoryInfo di = new DirectoryInfo(_modelPath);

            foreach (DirectoryInfo subDir in di.GetDirectories())
            {
                lstModels.Items.Add(subDir.Name);
            }

        }

        private void Train_Load(object sender, EventArgs e)
        {
            _modelPath = _workingPath + "Models\\";
            OpenModels();
            activeModel = new RecognitionModel();

            // FileStream fs = new FileStream(@"C:\Documents and Settings\Administrator\My Documents\Blimpbots\repository\CameraNode\Tracking\192-168-0-11.cam", FileMode.Open);
           // c.Properties = CameraProperties.ReadFrom(fs);
            //TestDistort();
        }

        private void ProcessFrame(Bitmap frame)
        {
            byte[] rawImage = null;
            using(MemoryStream ms = new MemoryStream())
            {
                frame.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
                rawImage = ms.ToArray();
            }

            ProcessFrame(frame, rawImage);
        }


        private void ProcessFrame(Bitmap frame, byte[] rawFrame)
        {
            RecognitionModel.Example newExample
                    = new RecognitionModel.Example(
                          frame, rawFrame, null);

            // Convert to grayscale image
            GrayscaleImage grayScene = new GrayscaleImage(frame);

            SurfLocal.Instance.DoubleImageSize = true;

            // Compute keypoints
            surfInterop.SurfKeypoint[] modelKeypoints =
                SurfLocal.Instance.FindKeypoints( grayScene, 4.0);

            /* request -all- of the keypoints */
            int[] indices = new int[modelKeypoints.Length];
            for (int i = 0; i < modelKeypoints.Length; i++)
                indices[i] = i;

            modelKeypoints =
                SurfLocal.Instance.DescribeKeypoints(indices);

            //MessageBox.Show("Count = " + modelKeypoints.Length.ToString());

            // Add keypoints to the example
            newExample.BackgroundKeypoints = new List<surfInterop.SurfKeypoint>(modelKeypoints.Length);

            foreach (surfInterop.SurfKeypoint kpt in modelKeypoints)
                newExample.BackgroundKeypoints.Add(kpt);

            activeModel.AddExample(newExample);
            AddExampleToGUI(newExample);

        }

        private LiveCamera c;

        private void btnCapture_Click(object sender, EventArgs e)
        {
            c.IP = txtIP.Text;
            Bitmap frame = c.LastFrame;

            chkPreview.Checked = false; // make sure preview is off

            if(frame == null)
            {
                MessageBox.Show("Error capturing.");
                return;
            }

            ProcessFrame(c.LastFrame, c.RawFrame);
        }

        private void RemoveExample(RecognitionModel.Example example)
        {
           
            FilmFrame remaining = null;

            for(int i = 0; i < flowImages.Controls.Count; i++)
            {
                if (flowImages.Controls[i].Tag == example)
                {
                    flowImages.Controls.RemoveAt(i);
                    break;
                }
                else
                    remaining = (FilmFrame)flowImages.Controls[i];
            }

            if (remaining != null)
            {
                activeExample = (RecognitionModel.Example)remaining.Tag;
                remaining.IsSelected = true;
            }
            else
            {
                activeExample = null;
            }
        }


        private void AddExampleToGUI(RecognitionModel.Example example)
        {
            FilmFrame thumb = new FilmFrame();
            thumb.Size = new Size(4 * 15, 3 * 15);
            thumb.Scene = example.Image;
            thumb.MouseDown += new MouseEventHandler(thumb_MouseDown);

            thumb.Tag = example;
            SelectFrame(thumb);
            flowImages.Controls.Add(thumb);

            //TODO: why did the flow control start resizing these?
            //      for now, resize it back manually ...
            thumb.Size = new Size(4 * 15, 3 * 15);
        }

        void thumb_MouseDown(object sender, MouseEventArgs e)
        {
            FilmFrame f = (FilmFrame)sender;
            SelectFrame(f);
        }

        private void SelectFrame(FilmFrame f)
        {
            foreach (Control c in flowImages.Controls)
            {
                FilmFrame ff = (FilmFrame)c;
                if (ff != f && ff.IsSelected)
                {
                    // Save the user's markings back into the example
                    UpdateExample(sceneBox.NamedPoints, sceneBox.Outline, sceneBox.InterestPoints, (RecognitionModel.Example)ff.Tag);
                    ff.IsSelected = false;
                }
            }
            
            // Extract the example object
            activeExample = (RecognitionModel.Example)f.Tag;
            f.IsSelected = true;
        }

        private List<PictureBox2.InterestPoint> BuildInterestPts(RecognitionModel.Example example)
        {
            List<PictureBox2.InterestPoint> result = new List<PictureBox2.InterestPoint>();

            bool hasStrength = false;
            int maxStrength = 1;
            if (example.KeypointStrength != null)
            {
                hasStrength = (example.KeypointStrength.Length
                            == example.ModelKeypoints.Count);

                foreach (int s in example.KeypointStrength)
                    maxStrength = Math.Max(maxStrength, s);
            }


            #region Copy the Model Keypoints in as Selected Interest Points
            if (example.ModelKeypoints != null)
            {

                for (int i = 0; i < example.ModelKeypoints.Count; i++)
                {
                    surfInterop.SurfKeypoint k = example.ModelKeypoints[i];
                    PictureBox2.InterestPoint ip = new PictureBox2.InterestPoint();
                    ip.Location = new Point((int)k.x, (int)k.y);
                    ip.Selected = true;
                    if (hasStrength)
                        ip.Size = (float)(1.5 + 8.0*((double)example.KeypointStrength[i] / maxStrength));

                    ip.Tag = k;
                    result.Add(ip);
                }
            }
            #endregion

            #region Copy the other Keypoints in as un-selected Interest Points
            if (example.BackgroundKeypoints != null)
            {
                foreach (surfInterop.SurfKeypoint k in example.BackgroundKeypoints)
                {
                    PictureBox2.InterestPoint ip = new PictureBox2.InterestPoint();
                    ip.Location = new Point((int)k.x, (int)k.y);
                    ip.Selected = false;
                    ip.Tag = k;
                    result.Add(ip);
                }
            }
            #endregion

            return result;

        }

        private void UpdateExample(Dictionary<string, Point> namedPoints, List<Point> objectOutline, List<PictureBox2.InterestPoint> iPts, 
                                    RecognitionModel.Example example)
        {
            example.ObjectOutline = objectOutline;
            example.UpdateNamedPoints(namedPoints);

            List<surfInterop.SurfKeypoint> modelKeypoints = new List<surfInterop.SurfKeypoint>();
            List<surfInterop.SurfKeypoint> bkgKeypoints = new List<surfInterop.SurfKeypoint>();

            // Shuffle the keypoints into the right list depending on
            // whether the user selected them or not.
            foreach (PictureBox2.InterestPoint iPt in iPts)
            {
                if(iPt.Selected)
                    modelKeypoints.Add((surfInterop.SurfKeypoint)iPt.Tag);
                else
                    bkgKeypoints.Add((surfInterop.SurfKeypoint)iPt.Tag);
            }

            example.ModelKeypoints = modelKeypoints;
            example.BackgroundKeypoints = bkgKeypoints;
            
        }

        private void btnNewModel_Click(object sender, EventArgs e)
        {
            if (!activeModel.HasName)
            {
                btnSaveModel.Enabled = false;
                txtNewModelName.Text = "";
                pnlNewModel.Visible = true;
                txtNewModelName.Focus();
            }
            else
            {
                ApplyLatestChanges();
                activeModel.Save(_modelPath);
            }
        }

        private void textBox1_TextChanged(object sender, EventArgs e)
        {

        }

        private void btnCloseNewDialog_Click(object sender, EventArgs e)
        {
            pnlNewModel.Visible = false;
            btnSaveModel.Enabled = true;
        }

        private void pnlNewModel_Paint(object sender, PaintEventArgs e)
        {

        }

        private void btnSaveNew_Click(object sender, EventArgs e)
        {
            if (txtNewModelName.Text.Trim() == "")
            {
                MessageBox.Show("You must enter a name.", "Error",
                    MessageBoxButtons.OK,  MessageBoxIcon.Warning);
            }
            else
            {
                pnlNewModel.Visible = false;

                // TODO: check for name existence
                activeModel.ModelName = txtNewModelName.Text;

                ApplyLatestChanges();
                activeModel.Save(_modelPath);

                btnSaveModel.Enabled = true;
            
            }
        }

        private void ApplyLatestChanges()
        {
            foreach (Control c in flowImages.Controls)
            {
                FilmFrame ff = (FilmFrame)c;
                if (ff.IsSelected)
                {
                    // Save the user's markings back into the example
                    UpdateExample(sceneBox.NamedPoints, sceneBox.Outline, sceneBox.InterestPoints, (RecognitionModel.Example)ff.Tag);
                }
            }
        }

        private void txtNewModelName_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
                btnSaveNew_Click(this, null);
        }

        private void lstModels_SelectedIndexChanged(object sender, EventArgs e)
        {
            RecognitionModel m = new RecognitionModel();
            m.Open((string)lstModels.SelectedItem, _modelPath);

            // Clear existing first
            flowImages.Controls.Clear();

            foreach (RecognitionModel.Example ex in m.Examples)
            {
                AddExampleToGUI(ex);
            }

            activeModel = m;

        }

        private Bitmap _correlatePad;
        private Graphics _correlateGraphics;

        private void btnCorrelate_Click(object sender, EventArgs e)
        {
            DialogResult r = MessageBox.Show("Self-correlation is optional and may take some time, depending on the number of keypoints per model and the number of examples.  Click 'Save' after performing correlation so that its results may be used to refine recognition.", "Recognition Training",  MessageBoxButtons.OKCancel);
            if (r == DialogResult.Cancel) return;

            ApplyLatestChanges();

            /* Create and show the correlation */
            _correlatePad = new Bitmap(activeExample.Image.Width, activeExample.Image.Height);
            _correlateGraphics = Graphics.FromImage(_correlatePad);

            picCorrelatePad.Image = _correlatePad;
            picCorrelatePad.Visible = true;
            /* ------------------------------- */

            activeModel.ModelReady += new EventHandler(activeModel_ModelReady);
            //activeModel.CorrelateExamples();

            int cC = activeModel.LearnCorrespondences();
            MessageBox.Show("Correspondence count = " + cC); 

            if (activeExample != null)
            {
                sceneBox.InterestPoints = BuildInterestPts(activeExample);
                sceneBox.Scene = activeExample.Image;
            }

            /* Clean up visualization */
            picCorrelatePad.Visible = false;
            _correlateGraphics.Dispose();
            _correlatePad.Dispose();

        }

        void activeModel_ModelReady(object sender, EventArgs e)
        {
            RecognitionModel.ModelReadyEventArgs args =
                (RecognitionModel.ModelReadyEventArgs)e;

            _correlateGraphics.DrawImage(args.ExampleB.Image, 0, 0);

            picCorrelatePad.Image = _correlatePad;

            Pen pen = new Pen(Color.Chartreuse, 4.0f);

            Visualization.ProjectBox(args.ExampleA.BoundingBox, args.AffineMdl.H, _correlateGraphics, pen);

            //picCorrelatePad.Invalidate();
            picCorrelatePad.Refresh();

        }

        private void flowImages_Paint(object sender, PaintEventArgs e)
        {

        }

        private void btnLoad_Click(object sender, EventArgs e)
        {
            DialogResult r = openDialog.ShowDialog();
            if (r == DialogResult.OK)
            {
                Bitmap frame = null;
                Bitmap frameBig = null;
                try{
                    double scale = 1.00;
                    frame = (Bitmap)Image.FromFile(openDialog.FileName);
                    frameBig = new Bitmap((int)(frame.Width * scale), (int)(frame.Height * scale));
                    Graphics g = Graphics.FromImage(frameBig);
                    g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                    g.DrawImage(frame, new RectangleF(0, 0, frameBig.Width, frameBig.Height));
                } catch {
                    MessageBox.Show("Error loading image.");
                    return;
                }

                ProcessFrame(frameBig);
            }

        }

        private void btnTrace_Click(object sender, EventArgs e)
        {
            if (sceneBox.Mode != PictureBox2.EditMode.OutlineDraw)
            {
                sceneBox.Outline.Clear();
                sceneBox.Mode = PictureBox2.EditMode.OutlineDraw;
                btnTrace.Text = "Done";
            }
            else
            {
                btnTrace.Text = "Trace";
                sceneBox.Mode = PictureBox2.EditMode.RegionEdit;
            }
        }

        private void txtNewNamedPoint_TextChanged(object sender, EventArgs e)
        {

        }

        private void txtNewNamedPoint_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                string[] nameDistPair = txtNewNamedPoint.Text.Split(new char[] { ',' });
                if (nameDistPair.Length != 2) return; // TODO: re-enable this
                string name = nameDistPair[0].Trim();
                double dist = 0;

                try { dist = Convert.ToDouble(nameDistPair[1]); }
                catch { return; /* Give up adding the point */ }

                if (activeExample.NamedPoints.ContainsKey(name))
                {
                    activeExample.NamedPoints[name].Distance = dist;
                }
                else
                {
                    activeExample.NamedPoints[name] = 
                        new NamedPoint(new Point(320, 240), dist);
                    lstNamedPoints.Items.Add(name);
                }

                // Select the last (new) item
                lstNamedPoints.SelectedIndex = lstNamedPoints.Items.Count - 1;
                txtNewNamedPoint.SelectAll();

            }
        }

        private void lstNamedPoints_SelectedIndexChanged(object sender, EventArgs e)
        {
            string selection = (string)lstNamedPoints.SelectedItem;
            sceneBox.SelectedNamedPoint = selection;
            sceneBox.Mode = PictureBox2.EditMode.SelectPoint;
            double dist = activeExample.NamedPoints[selection].Distance;
            lblDistance.Text = string.Format("Point distance: {0:f3} m", dist);
        }

        private void TestDistort()
        {
            picCorrelatePad.Visible = true;
            Bitmap b = new Bitmap(640, 480);
            using (Graphics g = Graphics.FromImage(b))
            {
                g.Clear(Color.White);

                foreach (int y in new int[] { 1, 47 })
                {
                    for (int x = 1; x <= 63; x++)
                    {
                        Point2D pos = new Point2D(x * 10, y * 10);
                        //g.DrawEllipse(Pens.Black, (float)pos.x, (float)pos.y4, 4F, 4F);

                        pos = c.Distort(pos);
                        g.DrawEllipse(Pens.Red, (float)pos.x, (float)pos.y, 4F, 4F);

                        pos = c.Undistort(pos);
                        g.DrawEllipse(Pens.Green, (float)pos.x, (float)pos.y, 4F, 4F);
                    }
                }

                foreach (int x in new int[] { 1, 63 })
                {
                    for (int y = 1; y <= 47; y++)
                    {
                        Point2D pos = new Point2D(x * 10, y * 10);
                        //g.DrawEllipse(Pens.Black, (float)pos.x, (float)pos.y4, 4F, 4F);

                        pos = c.Distort(pos);
                        g.DrawEllipse(Pens.Red, (float)pos.x, (float)pos.y, 4F, 4F);

                        pos = c.Undistort(pos);
                        g.DrawEllipse(Pens.Green, (float)pos.x, (float)pos.y, 4F, 4F);
                    }
                }





            }

            picCorrelatePad.Image = b;
        }

        private void chkPreview_CheckedChanged(object sender, EventArgs e)
        {
            if (chkPreview.Checked)
            {
                // c.IP = txtIP.Text;
                picCorrelatePad.Visible = true;
                c.Start();
            }
            else
            {
                picCorrelatePad.Visible = false;
                c.Stop();
            }
        }

        void c_NewFrame(object sender, EventArgs e)
        {
            picCorrelatePad.Image = c.LastFrame;
        }

        private void btnDelete_Click(object sender, EventArgs e)
        {
            if (activeExample != null)
            {
                DialogResult r = MessageBox.Show("Are you sure?  Click 'OK' to delete the currently selected example.", "Recognition Training", MessageBoxButtons.OKCancel);
                if (r == DialogResult.Cancel) return;
                RemoveExample(activeExample);
            }
        }


    }
}